AWS 署名 version4 の C 言語実装を読んでみた
以前、紹介した aws_dynamo に C 言語による aws 署名 version4 の実装が含まれているため、ソースコードリーディングしてみました。
公式ドキュメントにあるとおり aws 署名 version4 は 3 つの手順からなります。
- 正規リクエストの作成
- 署名文字列の作成
- 署名の作成
正規リクエストの作成
タスク 1: 署名バージョン 4 の正規リクエストを作成する は以下の関数で実装されています。
公式ドキュメントにある擬似コードをそれぞれ対応する箇所にコメントとして入れています。
C 言語ではハッシュの計算には OpenSSH ライブラリを使用することが多いです。
char *aws_sigv4_create_hashed_canonical_request(const char *http_request_method, const char *canonical_uri, const char *canonical_query_string, const char *canonical_headers, const char *signed_headers, const char *request_payload) { SHA256_CTX ctx; unsigned char hash[SHA256_DIGEST_LENGTH]; int i; unsigned char hex_hash[SHA256_DIGEST_LENGTH * 2 + 1]; char *canonical_request; /* HexHash = HexEncode(Hash(RequestPayload)) */ SHA256_Init(&ctx); SHA256_Update(&ctx, request_payload, strlen(request_payload)); SHA256_Final(hash, &ctx); /* 1 byte ずつ地道に 16 進文字表現を生成する */ for (i = 0; i < SHA256_DIGEST_LENGTH; i++) { sprintf(hex_hash + i * 2, "%.2x", hash[i]); } /* CanonicalRequest = HTTPRequestMethod + '\n' + CanonicalURI + '\n' + CanonicalQueryString + '\n' + CanonicalHeaders + '\n' + SignedHeaders + '\n' + HexHash */ if (asprintf(&canonical_request, "%s\n%s\n%s\n%s\n%s\n%s", http_request_method, canonical_uri, canonical_query_string, canonical_headers, signed_headers, hex_hash) == -1) { Warnx("aws_dynamo_create_signature: failed to create message"); return NULL; } /* HashedCanonicalRequest = HexEncode(Hash(CanonicalRequest)) */ SHA256_Init(&ctx); SHA256_Update(&ctx, canonical_request, strlen(canonical_request)); SHA256_Final(hash, &ctx); free(canonical_request); for (i = 0; i < SHA256_DIGEST_LENGTH; i++) { sprintf(hex_hash + i * 2, "%.2x", hash[i]); } return strdup(hex_hash); }
署名文字列の作成
タスク 2: 署名バージョン 4 の署名文字列を作成する は以下の関数で実装されています。
char *aws_sigv4_create_string_to_sign(char *iso8601_basic_date, char *yyyy_mm_dd, const char *region, const char *service, const char *hashed_canonical_request) { char *string_to_sign; /* StringToSign = Algorithm + '\n' + RequestDate + '\n' + CredentialScope + '\n' + HashedCanonicalRequest */ if (asprintf(&string_to_sign, "AWS4-HMAC-SHA256\n%s\n%s/%s/%s/aws4_request\n%s", iso8601_basic_date, yyyy_mm_dd, region, service, hashed_canonical_request) == -1) { Warnx("aws_dynamo_create_signature: failed to create string to sign."); return NULL; } return string_to_sign; }
署名の作成
タスク 3: AWS 署名バージョン 4 を計算する は以下の関数で実装されています。
int aws_sigv4_derive_signing_key( const char *aws_secret_access_key, const char *yyyy_mm_dd, const char *region, const char *service, unsigned char **key /* OUT */, int *key_len /* OUT */) { unsigned char md[EVP_MAX_MD_SIZE]; unsigned int md_len; unsigned char date_key[128]; int n; HMAC_CTX hmac_ctx; HMAC_CTX_init(&hmac_ctx); n = snprintf(date_key, sizeof(date_key), "AWS4%s", aws_secret_access_key); if (n < 0 || n > sizeof(date_key)) { Warnx("aws_sigv4_derive_signing_key: did not create date key."); return -1; } /* kDate = HMAC("AWS4" + kSecret, Date) */ HMAC_Init_ex(&hmac_ctx, date_key, strlen(date_key), EVP_sha256(), NULL); HMAC_Update(&hmac_ctx, yyyy_mm_dd, strlen(yyyy_mm_dd)); HMAC_Final(&hmac_ctx, md, &md_len); /* kRegion = HMAC(kDate, Region) */ HMAC_Init_ex(&hmac_ctx, md, md_len, EVP_sha256(), NULL); HMAC_Update(&hmac_ctx, region, strlen(region)); HMAC_Final(&hmac_ctx, md, &md_len); /* kService = HMAC(kRegion, Service) */ HMAC_Init_ex(&hmac_ctx, md, md_len, EVP_sha256(), NULL); HMAC_Update(&hmac_ctx, service, strlen(service)); HMAC_Final(&hmac_ctx, md, &md_len); /* kSigning = HMAC(kService, "aws4_request") */ HMAC_Init_ex(&hmac_ctx, md, md_len, EVP_sha256(), NULL); HMAC_Update(&hmac_ctx, "aws4_request", strlen("aws4_request")); HMAC_Final(&hmac_ctx, md, &md_len); HMAC_CTX_cleanup(&hmac_ctx); *key = malloc(md_len); if (*key == NULL) { Warnx("aws_sigv4_derive_signing_key: key alloc failed."); return -1; } memcpy(*key, md, md_len); *key_len = md_len; return 0; } char *aws_sigv4_create_signature( const char *aws_secret_access_key, const char *yyyy_mm_dd, const char *region, const char *service, const unsigned char *message) { HMAC_CTX hmac_ctx; unsigned char md[EVP_MAX_MD_SIZE]; unsigned int md_len; char *signature; int message_len = strlen(message); unsigned char *key = NULL; int key_len = 0; int i; HMAC_CTX_init(&hmac_ctx); aws_sigv4_derive_signing_key(aws_secret_access_key, yyyy_mm_dd, region, service, &key, &key_len); /* signature = HexEncode(HMAC(derived-signing-key, string-to-sign)) */ HMAC_Init_ex(&hmac_ctx, key, key_len, EVP_sha256(), NULL); HMAC_Update(&hmac_ctx, message, message_len); HMAC_Final(&hmac_ctx, md, &md_len); free(key); signature = malloc(md_len * 2 + 1); if (signature == NULL) { Warnx("aws_sigv4_create_signature: failed to allocate sig"); HMAC_CTX_cleanup(&hmac_ctx); return NULL; } for (i = 0; i < md_len; i++) { sprintf(signature + i * 2, "%.2x", md[i]); } HMAC_CTX_cleanup(&hmac_ctx); return signature; }
まとめ
C 言語の場合、特に文字列操作周りでコード量が多くなりがちにはなりますが、aws にネイティブコードで触ることができるのは特徴だと思いますので、興味のある方は触ってみてはいかがでしょうか。